﻿using System;
using System.Threading;
using System.Drawing;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Collections;
using System.Net;
using System.ComponentModel;
using ModbusTCP;

namespace AppMain
{
    class SwPlcComm
    {
        public string _guiText = "Initializing";
        public Color _guiTextForeColor = Color.Black;

        // Modbus Coils to Read Start at 3073
        private const int SwMatrixPlc_ReadCoilBaseAddress = 3081;
        private const int SwMatrixPlc_NumberOfReadCoils = 80;
        private const int SwMatrixPlc_WriteCoilBaseAddress = 3081;
        private const int SwMatrixPlc_NumberOfWriteCoilsWhenUpdatingDesiredPosition = 20;
        private const int SwMatrixPlc_NumberOfWriteCoilsWhenNotUpdatingDesiredPosition = 4;
        private const int SwMatrixPlc_ReadHoldingRegisterAddress = 768;
        private const int SwMatrixPlc_NumberOfReadHoldingRegisters = 4;
        private const int SwMatrixPlc_WriteHoldingRegisterAddress = 768;
        private const int SwMatrixPlc_NumberOfWriteHoldingRegisters = 4;

        // Data structures to support information read from or written to the PLC
        // These structures are kept separate with the intent to avoid accidental
        // overwriting of information from the user with information from the PLC
        private bool[] SwMatrixPlc_CoilsRead = new bool[SwMatrixPlc_NumberOfReadCoils+8];
        private bool[] SwMatrixPlc_CoilsToWrite = new bool[SwMatrixPlc_NumberOfWriteCoilsWhenUpdatingDesiredPosition + 12];
        private Int16[] SwMatrixPlc_HoldingRegistersRead = new Int16[SwMatrixPlc_NumberOfReadHoldingRegisters];
        private Int16[] SwMatrixPlc_HoldingRegistersToWrite = new Int16[SwMatrixPlc_NumberOfWriteHoldingRegisters];


        // Allow Referencing registers by their function, rather than by their number.
        // These numbers are relative, but will directly index into the structure returned
        // from the Modbus Library (note: The Individual Coils are Populated directly into
        // the Status Array without need for an Enum).
        public enum ReadCoilStatus
        {
            Fault_Block = 0,                //ModBus 3081 = Coils C10 - C22 in the PLC
            BulkFault = 11,                 //ModBus 3092 = Coil C23 in the PLC
            TransferRequested = 12,         //ModBus 3093 = Coil C24 in the PLC
            Reset = 14,                     //ModBus 3095 = Coil C26 in the PLC
            HeartBeat = 15,                 //ModBus 3096 = Coil C27 in the PLC
            DesiredPosition_Block = 16,     //ModBus 3097 = Coils C30 - C43 in the PLC
            TransmitterInhibit_Block = 28,  //ModBus 3109 = Coils C44 - C47 in the PLC
            InPositionA_Block = 32,         //ModBus 3113 = Coils C50 - C62 in the PLC
            InPositionB_Block = 48,         //ModBus 3129 = Coils C70 - C102 in the PLC
            Transferring_Block = 64,        //ModBus 3147 = Coils C110 - C122 in the PLC
        }
        public enum E_PlcEvent
        {
            DataReceived = 0,
            Error1,
            Error2
        }

        //Modbus variables
        private bool _commEnabled = false;
        private bool _commWasEnabled = false;
        private string _plcIpAddress;
        private bool _failedConnection = false;
        private bool _sendStatus = false;
        private static byte _failedReadWrites = 0;
        private static int _maximumConsecutiveReadWriteFailures = 12;

        private ModbusTCP.Master _swMatrix;
        public bool _modbusCommPortOpen = false;
        public bool _modbusReadPending = false;
        private const ushort C_ReadID = 1;
        private const ushort C_WriteID = 2;
        private const ushort H_ReadID = 3;
        private const ushort H_WriteID = 4;

        //Create instance of log data for logging from this file
        AppMain.LogData log = AppMain.LogData.GetObject;

        // Info log is configured even when a PLC connection doesn't exist
        LogInfo _infoLog = AppMain.LogInfo.GetObject;

        //Create Timer for polling data from PLC
        public System.Threading.Timer _tmrScanPLC;

        //Declaration for Application to respond to PLC data received
        private const int RetryTimeout = 5000;
        private int _scanTime;
        //public event PlcResponse OnPlcResponse;

        public string GetStatusText()
        {
            return _guiText;
        }
        //Method called to start communication with the PLC, setting simple parameters
        public void StartCommunication(string IpAddress, int scanTime)
        {   //Set PLC communication parameters
            _plcIpAddress = IpAddress;
            //Create Timer for polling data from PLC
            _tmrScanPLC = new System.Threading.Timer(tmrScanPLC_Tick, null, scanTime, scanTime);
            _scanTime = scanTime;
            //Enable cyclic read-write to PLC
            _commEnabled = true;
            _guiText = "Initializing Communications...";
        }

        //Cyclic timer fetch new data from PLC and write response
        void tmrScanPLC_Tick(object sender)
        {
            //If there was an error, decrease the update rate
            if (_commEnabled == false)
            {
                _tmrScanPLC.Change(RetryTimeout, RetryTimeout);
            }
            else
            {
                if (!_commWasEnabled)
                {
                    _tmrScanPLC.Change(_scanTime, _scanTime);
                }
            }
            _commWasEnabled = _commEnabled;
            //Open Modbus if not already connected
            if (!_modbusCommPortOpen)
            {
                // Note: This timer runs in a separate thread. There is no need for any
                // unique operations here.
                // keep this from connecting muliple ports
                if (!_modbusReadPending)
                {
                    connect();
                    _failedReadWrites = 0;
                }
            }
            else
            {
                if (!_swMatrix.connected)
                {
                    log.Write("Connection Strangely Lost.");
                    _modbusCommPortOpen = false;
                }
                //Don't start if we're still waiting for a response
                if (_modbusReadPending == false)
                {
                    //run method ReadWrite() in a background thread
                    //System.Threading.Thread ReadWriteThread = new System.Threading.Thread(ReadWrite);
                    //ReadWriteThread.Start();
                    ReadWrite();
                    if (_failedReadWrites > 0)
                    {
                        log.Write("Successful Switch Matrix Communication Attempt.");
                    }
                    _failedReadWrites = 0;
                }
                else
                {
                    log.Write("Failed Switch Matrix Communication Attempt.");
                    _failedReadWrites++;
                    if (_failedReadWrites > _maximumConsecutiveReadWriteFailures)
                    {
                        if (Disconnect())
                        {
                            log.Write(string.Format("Attempting to Restart Switch Matrix PLC Connection."));
                        }
                    }
                }
            }
            AppMain.SwMatrix._swMatrixPlcCommLoss = !_modbusCommPortOpen;
        }

        // Handle information reported back from the PLC. This should Read responses (valid data) and
        // Write responses (validity indication).
        private void OnPlcResponse(SwPlcComm.E_PlcEvent plcEvent, string guiText, Dictionary<int, int> requests)
        {
            //Update display status
            _guiText = guiText;

            if (plcEvent != SwPlcComm.E_PlcEvent.DataReceived)
            {   //Set color for error
                _guiTextForeColor = Color.Red;
            }
            else
            {
                _guiTextForeColor = Color.Black;
                _guiText = "Successfully Communicating";
            }
            _modbusReadPending = false;
        }


        //Master communication method which sends and receives data with PLC over Modbus
        private void ReadWrite()
        {
            _modbusReadPending = true; //Request is sent; new data is pending
            //Send only one command per cycle
            // NOTE: this has been ammended because the system operates correctly if all
            // requests are send simultaneously

            //               switch(SwMatrixPlc_CommState)
            //               {
            //                   case SwMatrixPlc_WriteCoils:
            //                   {
            //Toggle Heartbeat bit
            SwMatrixPlc_CoilsToWrite[Convert.ToInt32(ReadCoilStatus.HeartBeat)] = !SwMatrixPlc_CoilsRead[Convert.ToInt32(ReadCoilStatus.HeartBeat)];

            // Send Reset
            SwMatrixPlc_CoilsToWrite[Convert.ToInt32(ReadCoilStatus.Reset)] = AppMain.SwMatrix._resetPressed;
            
            if (AppMain.SwMatrix._transferInitiated && !AppMain.SwMatrix._transferSent)
            {
                AppMain.SwMatrix._transferComplete = false;
                // log Transfer Requested Event
                if (!SwMatrixPlc_CoilsToWrite[Convert.ToInt32(ReadCoilStatus.TransferRequested)] && AppMain.SwMatrix._dataVerified)
                {
                    _infoLog.Write("Transfer Request to Field Matrix PLC : ON");
                }
                SwMatrixPlc_CoilsToWrite[Convert.ToInt32(ReadCoilStatus.TransferRequested)] = AppMain.SwMatrix._dataVerified;
                // Inhibit Appropriate Transmitters
                // this function is used in two places, so it is protected by a Mutex
                AppMain.SwMatrix._updateInhibit.WaitOne();
                AppMain.SwMatrix.DetermineTransmittersToInhibit();
                for(byte x = 0; x< AppMain.SwMatrix.NumberOfTransmitters; x++)
                {
                    if (SwMatrixPlc_CoilsToWrite[Convert.ToInt32(ReadCoilStatus.TransmitterInhibit_Block) + x] != AppMain.SwMatrix._inhibitedTransmitters[x])
                    {
                        _infoLog.Write(string.Format("Field Matrix PLC: Transmitter {0} Inhibit. Was: {1}, Is: {2}", x + SwMatrix.MaximumTransmitterNumber - SwMatrix.NumberOfTransmitters + 1, SwMatrixPlc_CoilsToWrite[Convert.ToInt32(ReadCoilStatus.TransmitterInhibit_Block) + x], AppMain.SwMatrix._inhibitedTransmitters[x]));
                    }
                    SwMatrixPlc_CoilsToWrite[Convert.ToInt32(ReadCoilStatus.TransmitterInhibit_Block) + x] = AppMain.SwMatrix._inhibitedTransmitters[x];
                }
                AppMain.SwMatrix._updateInhibit.ReleaseMutex();
                // Update Information
                for (byte x = 0; x < AppMain.SwMatrix.NumberOfSupportedSwitches; x++)
                {
                    if (SwMatrixPlc_CoilsToWrite[Convert.ToInt32(ReadCoilStatus.DesiredPosition_Block) + x] != AppMain.SwMatrix._targetSwitchPositions[x])
                    {
                        _infoLog.Write(string.Format("Field Matrix PLC: Switch {0} Target Position. Was: {1}, Is: {2}, Actual Position is: {3}", x + 1, SwMatrixPlc_CoilsToWrite[Convert.ToInt32(ReadCoilStatus.DesiredPosition_Block) + x], AppMain.SwMatrix._targetSwitchPositions[x], AppMain.SwMatrix._switchPositions[x]));
                    }
                    SwMatrixPlc_CoilsToWrite[Convert.ToInt32(ReadCoilStatus.DesiredPosition_Block) + x] = AppMain.SwMatrix._targetSwitchPositions[x];
                }
                //Write Coils
                byte bytesToWrite = (SwMatrixPlc_NumberOfWriteCoilsWhenUpdatingDesiredPosition / 8) + 1;
                byte[] ByteArrayToWrite = new byte[bytesToWrite];
                byte workingByte = 0;
                byte byteBeingWritten = 0;
                for (byte x = 0; x < SwMatrixPlc_NumberOfWriteCoilsWhenUpdatingDesiredPosition; x++)
                {
                    workingByte >>= 1;
                    if (SwMatrixPlc_CoilsToWrite[(int)ReadCoilStatus.TransferRequested + x])
                    {
                        workingByte |= 0x80;
                    }
                    if ((x & 0x07) == 7)
                    {
                        ByteArrayToWrite[byteBeingWritten] = workingByte;
                        byteBeingWritten++;
                        workingByte = 0;
                    }
                }
                // handle the case where there are incomplete bytes
                if (workingByte > 0)
                {
                    workingByte >>= ((bytesToWrite * 8) - SwMatrixPlc_NumberOfWriteCoilsWhenUpdatingDesiredPosition);
                    ByteArrayToWrite[byteBeingWritten] = workingByte;
                }
                else
                {
                    ByteArrayToWrite[byteBeingWritten] = 0;
                }

                //Write Coils using Modbus
                _swMatrix.WriteMultipleCoils(C_WriteID, SwMatrixPlc_WriteCoilBaseAddress + (ushort)ReadCoilStatus.TransferRequested - 1, (ushort)SwMatrixPlc_NumberOfWriteCoilsWhenUpdatingDesiredPosition, ByteArrayToWrite);
            }
            else
            {
                if (_sendStatus)
                {
                    // The transfer Request Information should be sent all the time.
                    _sendStatus = false;
                    // log Transfer Requested Removal Event
                    if (SwMatrixPlc_CoilsToWrite[Convert.ToInt32(ReadCoilStatus.TransferRequested)] && AppMain.SwMatrix._transferComplete)
                    {
                        _infoLog.Write("Transfer Request to Field Matrix PLC : OFF");
                    }
                    SwMatrixPlc_CoilsToWrite[Convert.ToInt32(ReadCoilStatus.TransferRequested)] = !AppMain.SwMatrix._transferComplete;
                    //Write Coils
                    byte bytesToWrite = (SwMatrixPlc_NumberOfWriteCoilsWhenNotUpdatingDesiredPosition / 8) + 1;
                    byte[] ByteArrayToWrite = new byte[bytesToWrite];
                    byte workingByte = 0;
                    byte byteBeingWritten = 0;
                    for (byte x = 0; x < SwMatrixPlc_NumberOfWriteCoilsWhenNotUpdatingDesiredPosition; x++)
                    {
                        workingByte >>= 1;
                        if (SwMatrixPlc_CoilsToWrite[(int)ReadCoilStatus.TransferRequested + x])
                        {
                            workingByte |= 0x80;
                        }
                        if ((x & 0x07) == 7)
                        {
                            ByteArrayToWrite[byteBeingWritten] = workingByte;
                            byteBeingWritten++;
                            workingByte = 0;
                        }
                    }
                    // handle the case where there are incomplete bytes
                    if (workingByte > 0)
                    {
                        workingByte >>= ((bytesToWrite * 8) - SwMatrixPlc_NumberOfWriteCoilsWhenNotUpdatingDesiredPosition);
                        ByteArrayToWrite[byteBeingWritten] = workingByte;
                    }
                    else
                    {
                        ByteArrayToWrite[byteBeingWritten] = 0;
                    }

                    //Write Coils using Modbus
                    _swMatrix.WriteMultipleCoils(C_WriteID, SwMatrixPlc_WriteCoilBaseAddress + (ushort)ReadCoilStatus.TransferRequested - 1, (ushort)SwMatrixPlc_NumberOfWriteCoilsWhenNotUpdatingDesiredPosition, ByteArrayToWrite);
                }
                else
                {
                    // Transmitted Inhibits need to be sent all the time
                    _sendStatus = true;
                    // Inhibit Appropriate Transmitters
                    // this function is used in two places, so it is protected by a Mutex
                    AppMain.SwMatrix._updateInhibit.WaitOne();
                    AppMain.SwMatrix.DetermineTransmittersToInhibit();
                    for (byte x = 0; x < AppMain.SwMatrix.NumberOfTransmitters; x++)
                    {
                        if (SwMatrixPlc_CoilsToWrite[Convert.ToInt32(ReadCoilStatus.TransmitterInhibit_Block) + x] != AppMain.SwMatrix._inhibitedTransmitters[x])
                        {
                            _infoLog.Write(string.Format("Field Matrix PLC: Transmitter {0} Inhibit. Was: {1}, Is: {2}", x + SwMatrix.MaximumTransmitterNumber - SwMatrix.NumberOfTransmitters + 1, SwMatrixPlc_CoilsToWrite[Convert.ToInt32(ReadCoilStatus.TransmitterInhibit_Block) + x], AppMain.SwMatrix._inhibitedTransmitters[x]));
                        }
                        SwMatrixPlc_CoilsToWrite[Convert.ToInt32(ReadCoilStatus.TransmitterInhibit_Block) + x] = AppMain.SwMatrix._inhibitedTransmitters[x];
                    }
                    AppMain.SwMatrix._updateInhibit.ReleaseMutex();
                    //Write Coils
                    byte bytesToWrite = (SwMatrixPlc_NumberOfWriteCoilsWhenNotUpdatingDesiredPosition / 8) + 1;
                    byte[] ByteArrayToWrite = new byte[bytesToWrite];
                    byte workingByte = 0;
                    byte byteBeingWritten = 0;
                    for (byte x = 0; x < SwMatrixPlc_NumberOfWriteCoilsWhenNotUpdatingDesiredPosition; x++)
                    {
                        workingByte >>= 1;
                        if (SwMatrixPlc_CoilsToWrite[(int)ReadCoilStatus.TransmitterInhibit_Block + x])
                        {
                            workingByte |= 0x80;
                        }
                        if ((x & 0x07) == 7)
                        {
                            ByteArrayToWrite[byteBeingWritten] = workingByte;
                            byteBeingWritten++;
                            workingByte = 0;
                        }
                    }
                    // handle the case where there are incomplete bytes
                    if (workingByte > 0)
                    {
                        workingByte >>= ((bytesToWrite * 8) - SwMatrixPlc_NumberOfWriteCoilsWhenNotUpdatingDesiredPosition);
                        ByteArrayToWrite[byteBeingWritten] = workingByte;
                    }
                    else
                    {
                        ByteArrayToWrite[byteBeingWritten] = 0;
                    }

                    //Write Coils using Modbus
                    _swMatrix.WriteMultipleCoils(C_WriteID, SwMatrixPlc_WriteCoilBaseAddress + (ushort)ReadCoilStatus.TransmitterInhibit_Block - 1, (ushort)SwMatrixPlc_NumberOfWriteCoilsWhenNotUpdatingDesiredPosition, ByteArrayToWrite);
                }
            }

            //Read Coils
            _swMatrix.ReadCoils(C_ReadID, SwMatrixPlc_ReadCoilBaseAddress - 1, SwMatrixPlc_NumberOfReadCoils);
            //                        break;
            //                    }
            //                    case SwMatrixPlc_WriteHoldingRegisters:
            //                    {
            //Write Holding Registers
            if (AppMain.SwMatrix._registersChanged)
            {
                byte[] ByteArrayToWrite = new byte[SwMatrixPlc_NumberOfWriteHoldingRegisters * 2];
                for( byte x = 0; x < 4; x++)
                {
                    ByteArrayToWrite[2 * x] = (byte)(AppMain.SwMatrix._dataToBeWritten[x] / 256);
                    ByteArrayToWrite[(2 * x) + 1] = (byte)(AppMain.SwMatrix._dataToBeWritten[x] & 0xFF);
                }

                _swMatrix.WriteMultipleRegister(H_WriteID, SwMatrixPlc_WriteHoldingRegisterAddress, ByteArrayToWrite);
                AppMain.SwMatrix._registersChanged = false;
            }
            //                        break;
            //                    }
            //                    case SwMatrixPlc_ReadHoldingRegisters:
            //                    {
            //Read Holding Registers
            _swMatrix.ReadHoldingRegister(H_ReadID, SwMatrixPlc_ReadHoldingRegisterAddress, SwMatrixPlc_NumberOfReadHoldingRegisters);
            //                        break;
            //                    }
            //            }
        }


        private void connect()
        {
            if (!_failedConnection)
            {
                log.Write(string.Format("Attempting to connect to PLC: {0}", _plcIpAddress));
            }

            // once a connection has been attempted, don't try it again
            try
            {
                // make sure that the Read/Write function is not called
                // until it is configured.
                _modbusReadPending = true;
                // Create new modbus master and add event functions
                _swMatrix = new Master(_plcIpAddress, 502);
                _swMatrix.OnResponseData += new ModbusTCP.Master.ResponseData(MBmaster_OnResponseData);
                _swMatrix.OnException += new Master.ExceptionData(MBmaster_OnException);
                // Show additional fields, enable watchdog
                //Start continuous communication
                log.Write(string.Format("Connection with PLC successful."));
                // Inform the world that the thread is open so that it doesn't continually retry
                _modbusCommPortOpen = true;
                _failedConnection = false;
                _modbusReadPending = false;
                // Active all the time
                //_infoLog = AppMain.LogInfo.GetObject;
            }
            catch (SystemException error)
            {
                if (!_failedConnection)
                {
                    log.Write(string.Format("Error connecting to PLC! {0}", error.Message));
                }
                _guiText = "PLC Communication Error. Check Log.";
                _modbusReadPending = false;
                _commEnabled = false;
                OnPlcResponse(E_PlcEvent.Error1, _guiText, null);
                _failedConnection = true;
                _modbusCommPortOpen = false;
            }
        }

        private bool Disconnect()
        {
            try
            {
                log.Write(string.Format("Attempting to disconnect Switch Matrix PLC Connection."));
                _swMatrix.disconnect();
                _modbusReadPending = false;
                _commEnabled = false;
                _failedConnection = true;
                _modbusCommPortOpen = false;
                return true;
            }
            catch (Exception)
            {
                if (!_failedConnection)
                {
                    log.Write(string.Format("Error disconnecting from Switch Matrix PLC!"));
                }
                _guiText = "Please restart Switchmatrix Program.";
                _modbusReadPending = false;
                _commEnabled = false;
                _failedConnection = true;
                _modbusCommPortOpen = true;
                return false;
            }
        }

        // ------------------------------------------------------------------------
        // Event for response data
        // ------------------------------------------------------------------------
        private void MBmaster_OnResponseData(ushort ID, byte function, byte[] values)
        {
            // ------------------------------------------------------------------
            // Seperate calling threa

            switch (ID)
            {
                default:
                // ignore Data from Write requests
                case C_WriteID:
                case H_WriteID:
                case 0xFF:
                    {
                        return;
                    }

                // Coil Read Request
                case C_ReadID:
                    {
                        BitArray PlcRxData = new BitArray(values);
                        PlcRxData.CopyTo(SwMatrixPlc_CoilsRead, 0);
                        ProcessCoils();
                        if (AppMain.SwMatrix._transferInitiated)
                        {
                            AppMain.SwMatrix._dataVerified = ComparePLCDataWithWrittenData();
                        }
                        _guiTextForeColor = Color.Black;
                        _guiText = "Switch Matrix PLC Communication working normally";
                        break;
                    }
                // Holding Register Read Request
                case H_ReadID:
                    {
                        byte y = 0;
                        for (byte x = 0; x < (values.Length) / 2; x++)
                        {
                            Int16 tempVar = values[y++];
                            tempVar *= 256;
                            tempVar += values[y++];
                            SwMatrixPlc_HoldingRegistersRead[x] = tempVar;
                        }
                        ProcessHoldingRegisters();
                        // The entire message has returned
                        _modbusReadPending = false;
                        break;
                    }
            }
        }

        // ------------------------------------------------------------------------
        // Modbus TCP slave exception
        // ------------------------------------------------------------------------
        private void MBmaster_OnException(ushort id, byte function, byte exception)
        {
            string exc = "Switch Matrix PLC Error! Modbus: ";
            switch (exception)
            {
                case Master.excIllegalFunction: exc += "Illegal function!"; break;
                case Master.excIllegalDataAdr: exc += "Illegal data adress!"; break;
                case Master.excIllegalDataVal: exc += "Illegal data value!"; break;
                case Master.excSlaveDeviceFailure: exc += "Slave device failure!"; break;
                case Master.excAck: exc += "Acknowledge!"; break;
                case Master.excSlaveIsBusy: exc += "Slave is busy!"; break;
                case Master.excGatePathUnavailable: exc += "Gateway path unavailable!"; break;
                case Master.excExceptionTimeout: exc += "Slave timed out!"; break;
                case Master.excExceptionConnectionLost: exc += "Connection is lost!"; break;
                case Master.excExceptionNotConnected: exc += "Not connected!"; break;
            }
            log.Write(exc + "Modbus slave exception");
            _commEnabled = false;
            // Make sure that the system forgets about the pending read
            _modbusReadPending = false;
            // Retry the connection
            _modbusCommPortOpen = false;
            OnPlcResponse(E_PlcEvent.Error2, _guiText, null);
        }

        private void ProcessCoils()
        {
            for (byte x = 0; x < AppMain.SwMatrix.NumberOfSupportedSwitches; x++)
            {
                if (SwMatrixPlc_CoilsRead[Convert.ToInt32(ReadCoilStatus.InPositionA_Block) + x])
                {
                    if (SwMatrixPlc_CoilsRead[Convert.ToInt32(ReadCoilStatus.InPositionB_Block) + x])
                    {
                        if (AppMain.SwMatrix._switchPositions[x] != (byte)AppMain.SwMatrix.SwitchGraphic.PositionError)
                        {
                            _infoLog.Write(string.Format("Field Matrix PLC: Switch {0} Switched to Invalid Position", x + 1));
                        }
                        AppMain.SwMatrix._switchPositions[x] = (byte)AppMain.SwMatrix.SwitchGraphic.PositionError;
                    }
                    else
                    {
                        if (AppMain.SwMatrix._switchPositions[x] != (byte)AppMain.SwMatrix.SwitchGraphic.Position0)
                        {
                            _infoLog.Write(string.Format("Field Matrix PLC: Switch {0} Switched to Position A", x + 1));
                        }
                        AppMain.SwMatrix._switchPositions[x] = (byte)AppMain.SwMatrix.SwitchGraphic.Position0;
                    }
                }
                else
                {
                    if (SwMatrixPlc_CoilsRead[Convert.ToInt32(ReadCoilStatus.InPositionB_Block) + x])
                    {
                        if (AppMain.SwMatrix._switchPositions[x] != (byte)AppMain.SwMatrix.SwitchGraphic.Position1)
                        {
                            _infoLog.Write(string.Format("Field Matrix PLC: Switch {0} Switched to Position B", x + 1));
                        }
                        AppMain.SwMatrix._switchPositions[x] = (byte)AppMain.SwMatrix.SwitchGraphic.Position1;
                    }
                    else
                    {
                        if (AppMain.SwMatrix._switchPositions[x] != (byte)AppMain.SwMatrix.SwitchGraphic.Moving)
                        {
                            _infoLog.Write(string.Format("Field Matrix PLC: Switch {0} Started Moving", x + 1));
                        }
                        AppMain.SwMatrix._switchPositions[x] = (byte)AppMain.SwMatrix.SwitchGraphic.Moving;
                    }
                }
                if (AppMain.SwMatrix._switchError[x] != SwMatrixPlc_CoilsRead[Convert.ToInt32(ReadCoilStatus.Fault_Block) + x])
                {
                    _infoLog.Write(string.Format("Field Matrix PLC: Switch {0} HAS AN ERROR!", x + 1));
                }
                AppMain.SwMatrix._switchError[x] = SwMatrixPlc_CoilsRead[Convert.ToInt32(ReadCoilStatus.Fault_Block) + x];
            }
            if (SwMatrixPlc_CoilsRead[Convert.ToInt32(ReadCoilStatus.Reset)])
            {
                _infoLog.Write("Reset was sent to Field Matrix PLC");
                AppMain.SwMatrix._resetPressed = false;
            }
            // If the transfer was sent, make sure to not send it again
            if(SwMatrixPlc_CoilsRead[Convert.ToInt32(ReadCoilStatus.TransferRequested)])
            {
                // This is handled below, where the data is actually changed
                //_infoLog.Write("Field Matrix PLC acknowledged receipt of transfer request");
                AppMain.SwMatrix._transferSent = true;
            }
        }
        private void ProcessHoldingRegisters()
        {
            if (!AppMain.SwMatrix._registersChanged)
            {
                for (byte x = 0; x < 4; x++)
                {
                    AppMain.SwMatrix._dataToBeWritten[x] = SwMatrixPlc_HoldingRegistersRead[x];
                }
            }
        }
        private bool ComparePLCDataWithWrittenData()
        {
            bool dataMatches = true;
            for(byte x = 0; x < AppMain.SwMatrix.NumberOfTransmitters; x++)
            {
                if(SwMatrixPlc_CoilsRead[Convert.ToInt32(ReadCoilStatus.TransmitterInhibit_Block) + x] != SwMatrixPlc_CoilsToWrite[Convert.ToInt32(ReadCoilStatus.TransmitterInhibit_Block) + x])
                {
                    dataMatches = false;
                }
            }
            for (byte x = 0; x < AppMain.SwMatrix.NumberOfSupportedSwitches; x++)
            {
                if (SwMatrixPlc_CoilsRead[Convert.ToInt32(ReadCoilStatus.DesiredPosition_Block) + x] != SwMatrixPlc_CoilsToWrite[Convert.ToInt32(ReadCoilStatus.DesiredPosition_Block) + x])
                {
                    dataMatches = false;
                }
            }
            if (!dataMatches)
            {
                _infoLog.Write("Data returned from Field Matrix PLC does not match written data");
            }
            // If the PLC has seen that the Transfer is initiated, there is no more need to set it in
            // software
            if (SwMatrixPlc_CoilsRead[Convert.ToInt32(ReadCoilStatus.TransferRequested)])
            {
                AppMain.SwMatrix._transferInitiated = false;
                // Transfer Active is turned on when the PLC sees the command.
                AppMain.SwMatrix._transferActive = true;
                AppMain.SwMatrix._aTransferWasActive = false;
                _infoLog.Write("Field Matrix PLC acknowledged receipt of transfer request");
            }
            // a transfer ceases to be active if none of the Transferring bits are active
            // (these should come active as soon as the Transfer Requested is turned off
            // by the PLC)
            if (AppMain.SwMatrix._transferActive)
            {
                bool transferActive = false;
                for (byte x = 0; x < AppMain.SwMatrix.NumberOfSupportedSwitches; x++)
                {
                    if (SwMatrixPlc_CoilsRead[Convert.ToInt32(ReadCoilStatus.Transferring_Block) + x])
                    {
                        transferActive = true;
                    }
                }
                // Make sure that the Initiate Transfer bit is cleared after the transfer is complete.
                if (!transferActive && AppMain.SwMatrix._aTransferWasActive)
                {
                    AppMain.SwMatrix._transferComplete = true;
                }
                else
                {
                    // If a transfer was never active, there is no reason to clear it. At least one switch should try to move.
                    if (!AppMain.SwMatrix._aTransferWasActive)
                    {
                        transferActive = AppMain.SwMatrix._transferActive;
                    }
                    AppMain.SwMatrix._aTransferWasActive = transferActive;
                }
                AppMain.SwMatrix._transferActive = transferActive;
                if (!transferActive)
                {
                    _infoLog.Write("Field Matrix Indicates that Transfer is Complete!");
                }
            }
            return dataMatches;
        }
    }

    class BurkPlcComm
    {
        public string _guiText = "Initializing";
        public Color _guiTextForeColor = Color.Black;

        // Modbus Coils to Read Start at 3073 = C0 so 3076 = C3
        private const int BurkCommPlc_ReadCoilBaseAddress = 3076;
        private const int BurkCommPlc_NumberOfReadCoils = 22;
        // The first 6 registers are read-only.
        private const int BurkCommPlc_WriteCoilBaseAddress = BurkCommPlc_ReadCoilBaseAddress + 6;
        private const int BurkCommPlc_NumberOfWriteCoils = 9;
        private const int BurkCommPlc_ReadHoldingRegisterAddress = 768;
        private const int BurkCommPlc_NumberOfReadHoldingRegisters = 4;
        private bool _dataVerified = false;
        private bool _dataWasVerified = false;
        private bool _transferRequestReceived = true;
        public static byte _lastPlcTransaction = 0;
        public static byte _trackPlcTransmissions = 0;
        public static byte _trackPlcReceptions = 0;
        private static byte _failedReadWrites = 0;
        private static int _maximumConsecutiveReadWriteFailures = 12;

        // Data structures to support information read from or written to the PLC
        // These structures are kept separate with the intent to avoid accidental
        // overwriting of information from the user with information from the PLC
        private bool[] BurkCommPlc_CoilsRead = new bool[BurkCommPlc_NumberOfReadCoils + 8];
        private bool[] BurkCommPlc_CoilsToWrite = new bool[BurkCommPlc_NumberOfWriteCoils + 6];
        private Int16[] BurkCommPlc_HoldingRegistersRead = new Int16[BurkCommPlc_NumberOfReadHoldingRegisters];

        // Allow Referencing registers by their function, rather than by their number.
        // These numbers are relative, but will directly index into the structure returned
        // from the Modbus Library (note: The Individual Coils are Populated directly into
        // the Status Array without need for an Enum).
        public enum ReadCoilStatus
        {
            Reset = 0,              // Modbus 3076 = Coil C3 in the PLC
            TransferRequested = 1,  // Modbus 3077 = Coil C4 in the PLC
            Lock_Block = 2,         // Modbus 3078 = Coils C5 - C10 in the PLC (Block of 4)
            Acknowledge = 6,        // Modbus 3082 = Coil C11 in the PLC
            Alarm_Active = 7,       // Modbus 3083 = Coil C12 in the PLC
            Comm_Fault = 8,         // Modbus 3084 = Coil C13 in the PLC
            Heartbeat = 9,          // Modbus 3085 = Coil C14 in the PLC
            Inhibit_Block = 10,     // Modbus 3086 = Coils C15 - C20 in the PLC (Block of 4)
            TransferError = 14,     // Modbus 3090 = Coil C21 in the PLC
            AntennaNotReady = 15,   // Modbus 3091 = Coils C22 - C30 in the PLC (Block of 7)
        }
        public enum E_PlcEvent
        {
            DataReceived = 0,
            Error1,
            Error2
        }

        //Modbus variables
        private bool _commEnabled = false;
        private bool _commWasEnabled = false;
        private string _plcIpAddress;
        private bool _failedConnection = false;

        private ModbusTCP.Master _burkComm;
        public bool _modbusCommPortOpen = false;
        public bool _modbusReadPending = false;
        private const ushort C_ReadID = 1;
        private const ushort C_WriteID = 2;
        private const ushort H_ReadID = 3;
        private const ushort H_WriteID = 4;

        //Create instance of log data for logging from this file
        AppMain.LogData log = AppMain.LogData.GetObject;

        // Info log is configured even when a PLC connection doesn't exist
        LogInfo _infoLog = AppMain.LogInfo.GetObject;

        //Create Timer for polling data from PLC
        public System.Threading.Timer tmrScanPLC;

        //Declaration for Application to respond to PLC data received
        private const int RetryTimeout = 5000;
        private int _scanTime;
        //public event PlcResponse OnPlcResponse;

        public string GetStatusText()
        {
            return _guiText;
        }
        //Method called to start communication with the PLC, setting simple parameters
        public void StartCommunication(string IpAddress, int scanTime)
        {   //Set PLC communication parameters
            _plcIpAddress = IpAddress;
            //Create Timer for polling data from PLC
            tmrScanPLC = new System.Threading.Timer(tmrScanPLC_Tick, null, scanTime, scanTime);
            _scanTime = scanTime;
            //Enable cyclic read-write to PLC
            _commEnabled = true;
            _guiText = "Initializing Communications...";
        }

        //Cyclic timer fetch new data from PLC and write response
        void tmrScanPLC_Tick(object sender)
        {
            //If there was an error, decrease the update rate
            if (_commEnabled == false)
            {
                tmrScanPLC.Change(RetryTimeout, RetryTimeout);
            }
            else
            {
                if (!_commWasEnabled)
                {
                    tmrScanPLC.Change(_scanTime, _scanTime);
                }
            }
            _commWasEnabled = _commEnabled;
            //Open Modbus if not already connected
            if (!_modbusCommPortOpen)
            {
                // Note: This timer runs in a separate thread. There is no need for any
                // unique operations here.
                // keep this from connecting muliple ports
                if (!_modbusReadPending)
                {
                    connect();
                    _failedReadWrites = 0;
                }
            }
            else
            {
                if (!_burkComm.connected)
                {
                    _modbusCommPortOpen = false;
                }
                //Don't start if we're still waiting for a response
                if (_modbusReadPending == false)
                {
                    //run method ReadWrite() in a background thread
                    //System.Threading.Thread ReadWriteThread = new System.Threading.Thread(ReadWrite);
                    //ReadWriteThread.Start();
                    ReadWrite();
                    if (_failedReadWrites > 0)
                    {
                        log.Write("Successful Burk Communication Attempt.");
                    }
                    _failedReadWrites = 0;
                }
                else
                {
                    log.Write("Failed Burk Communication Attempt.");
                    _failedReadWrites++;
                    if (_failedReadWrites > _maximumConsecutiveReadWriteFailures)
                    {
                        if (Disconnect())
                        {
                            log.Write(string.Format("Attempting to Restart Burk PLC Connection."));
                        }
                    }
                }
            }
        }

        // Handle information reported back from the PLC. This should Read responses (valid data) and
        // Write responses (validity indication).
        private void OnPlcResponse(BurkPlcComm.E_PlcEvent plcEvent, string guiText, Dictionary<int, int> requests)
        {
            //Update display status
            _guiText = guiText;

            if (plcEvent != BurkPlcComm.E_PlcEvent.DataReceived)
            {   //Set color for error
                _guiTextForeColor = Color.Red;
            }
            else
            {
                _guiTextForeColor = Color.Black;
                _guiText = "Successfully Communicating";
            }
            _modbusReadPending = false;
        }


        //Master communication method which sends and receives data with PLC over Modbus
        private void ReadWrite()
        {
            _modbusReadPending = true; //Request is sent; new data is pending
            
            // Holding Registers (containing desired Transfer Scenario) have been read
            // Provide Receipt to Burk Communication PLC
            // and Initiate Transfer
            bool alarmActive = false;
            for( byte x = 0; x < SwMatrix.NumberOfSupportedSwitches; x++)
            {
                if(SwMatrix._switchError[x])
                {
                    alarmActive = true;
                }
            }

            if (!BurkCommPlc_CoilsToWrite[Convert.ToInt32(ReadCoilStatus.Acknowledge)] && (_dataVerified && BurkCommPlc_CoilsRead[(int)ReadCoilStatus.TransferRequested]))
            {
                _infoLog.Write("Transfer Request from Burk PLC Acknowledged by Switch Matrix GUI");
            }
            BurkCommPlc_CoilsToWrite[Convert.ToInt32(ReadCoilStatus.Acknowledge)] = _dataVerified && BurkCommPlc_CoilsRead[(int)ReadCoilStatus.TransferRequested];
            BurkCommPlc_CoilsToWrite[Convert.ToInt32(ReadCoilStatus.Alarm_Active)] = alarmActive;
            BurkCommPlc_CoilsToWrite[Convert.ToInt32(ReadCoilStatus.Comm_Fault)] = SwMatrix._swMatrixPlcCommLoss;
            BurkCommPlc_CoilsToWrite[Convert.ToInt32(ReadCoilStatus.Heartbeat)] = !BurkCommPlc_CoilsRead[(int)ReadCoilStatus.Heartbeat];
            // make sure that this function is not accessed by both functions at the same time
            AppMain.SwMatrix._updateInhibit.WaitOne();
            AppMain.SwMatrix.DetermineTransmittersToInhibit();
            for(byte x = 0; x< AppMain.SwMatrix.NumberOfTransmitters; x++)
            {
                if (BurkCommPlc_CoilsToWrite[Convert.ToInt32(ReadCoilStatus.Inhibit_Block) + x] != AppMain.SwMatrix._inhibitedTransmitters[x])
                {
                    _infoLog.Write(string.Format("Burk PLC: Transmitter {0} Inhibit. Was: {1}, Is: {2}", x + SwMatrix.MaximumTransmitterNumber - SwMatrix.NumberOfTransmitters + 1, BurkCommPlc_CoilsToWrite[Convert.ToInt32(ReadCoilStatus.Inhibit_Block) + x], AppMain.SwMatrix._inhibitedTransmitters[x]));
                }
                BurkCommPlc_CoilsToWrite[Convert.ToInt32(ReadCoilStatus.Inhibit_Block) + x] = AppMain.SwMatrix._inhibitedTransmitters[x];
            }
            AppMain.SwMatrix._updateInhibit.ReleaseMutex();
            // Ensure that the switches match their desired configuration
            bool transferError = false;
            for(byte x = 0; x < AppMain.SwMatrix.NumberOfSupportedSwitches; x++)
            {
                 if((((SwMatrix._switchPositions[x] != (byte)AppMain.SwMatrix.SwitchGraphic.Position1) && SwMatrix._targetSwitchPositions[x]) || (SwMatrix._switchPositions[x] != (byte)AppMain.SwMatrix.SwitchGraphic.Position0 && !SwMatrix._targetSwitchPositions[x])))
                 {
                     transferError = true;
                 }
            }
            if (!BurkCommPlc_CoilsToWrite[Convert.ToInt32(ReadCoilStatus.TransferError)] && transferError)
            {
                _infoLog.Write("Transfer Error to Burk PLC: ON");
            }
            if (BurkCommPlc_CoilsToWrite[Convert.ToInt32(ReadCoilStatus.TransferError)] && !transferError)
            {
                _infoLog.Write("Transfer Error to Burk PLC: OFF");
            }
            BurkCommPlc_CoilsToWrite[Convert.ToInt32(ReadCoilStatus.TransferError)] = transferError;
            // If information needs to be Written, Write it
            byte bytesToWrite = (BurkCommPlc_NumberOfWriteCoils / 8) + 1;
            byte[] ByteArrayToWrite = new byte[bytesToWrite];
            byte workingByte = 0;
            byte byteBeingWritten = 0;
            for (byte x = 0; x < BurkCommPlc_NumberOfWriteCoils; x++)
            {
                workingByte >>= 1;
                if (BurkCommPlc_CoilsToWrite[(int)ReadCoilStatus.Acknowledge + x])
                {
                    workingByte |= 0x80;
                }
                if ((x & 0x07) == 7)
                {
                    ByteArrayToWrite[byteBeingWritten] = workingByte;
                    byteBeingWritten++;
                    workingByte = 0;
                }
            }
            // handle the case where there are incomplete bytes
            if (workingByte > 0)
            {
                workingByte >>= ((bytesToWrite * 8) - BurkCommPlc_NumberOfWriteCoils);
                ByteArrayToWrite[byteBeingWritten] = workingByte;
            }
            else
            {
                ByteArrayToWrite[byteBeingWritten] = 0;
            }

            //Write Coils using Modbus
            _burkComm.WriteMultipleCoils(C_WriteID, BurkCommPlc_WriteCoilBaseAddress - 1, (ushort)BurkCommPlc_NumberOfWriteCoils, ByteArrayToWrite);
            _lastPlcTransaction = ByteArrayToWrite[0];

            if(_dataVerified && !_dataWasVerified)
            {
                // Initiate Transfer
                byte alternates = SwMatrix.DetermineOptimalSwitchConfiguration();
                _infoLog.Write(string.Format("Burk Initiated Transfer: Number of valid switch configurations = {0}", alternates));
                if (alternates > 0)
                {
                    // Update the Target configuration with the least cost alternative
                    SwMatrix.UpdateWorkingSwitchConfigWithAlternate(0);
                    _infoLog.Write(string.Format("Burk Initiated Transfer: Using least cost alternative, {0} switches to change", SwMatrix._configCost));
                    for (byte x = 0; x < SwMatrix.NumberOfSupportedSwitches; x++)
                    {
                        SwMatrix._targetSwitchPositions[x] = SwMatrix._workingTargetSwitchPositions[x];
                    }
                    if (SwMatrix._configCost > 0)
                    {
                        // Only Call Transfer if there are switches to change
                        SwMatrix.InitiateTransferSequence();
                    }
                }
            }

            _dataWasVerified = _dataVerified;

            _burkComm.ReadCoils(C_ReadID, BurkCommPlc_ReadCoilBaseAddress - 1, BurkCommPlc_NumberOfReadCoils);

            //Read Holding Registers
            if (!_dataVerified)
            {
                _burkComm.ReadHoldingRegister(H_ReadID, BurkCommPlc_ReadHoldingRegisterAddress, BurkCommPlc_NumberOfReadHoldingRegisters);
            }
        }


        private void connect()
        {
            if (!_failedConnection)
            {
                log.Write(string.Format("Attempting to connect to PLC: {0}", _plcIpAddress));
            }

            // once a connection has been attempted, don't try it again
            try
            {
                // make sure that the Read/Write function is not called
                // until it is configured.
                _modbusReadPending = true;
                // Create new modbus master and add event functions
                _burkComm = new Master(_plcIpAddress, 502);
                _burkComm.OnResponseData += new ModbusTCP.Master.ResponseData(MBmaster_OnResponseData);
                _burkComm.OnException += new Master.ExceptionData(MBmaster_OnException);
                // Show additional fields, enable watchdog
                //Start continuous communication
                log.Write(string.Format("Connection with PLC successful."));
                // Inform the world that the thread is open so that it doesn't continually retry
                _modbusCommPortOpen = true;
                _failedConnection = false;
                _modbusReadPending = false;
                // Active all the time
                //_infoLog = AppMain.LogInfo.GetObject;
            }
            catch (SystemException error)
            {
                if (!_failedConnection)
                {
                    log.Write(string.Format("Error connecting to PLC! {0}", error.Message));
                }
                _guiText = "PLC Communication Error. Check Log.";
                _modbusReadPending = false;
                _commEnabled = false;
                OnPlcResponse(E_PlcEvent.Error1, _guiText, null);
                _failedConnection = true;
                _modbusCommPortOpen = false;
            }
        }

        private bool Disconnect()
        {
            try
            {
                log.Write(string.Format("Attempting to disconnect Burk PLC Connection."));
                _burkComm.disconnect();
                _modbusReadPending = false;
                _commEnabled = false;
                _failedConnection = true;
                _modbusCommPortOpen = false;
                return true;
            }
            catch(Exception)
            {
                if (!_failedConnection)
                {
                    log.Write(string.Format("Error disconnecting from Burk PLC!"));
                }
                _guiText = "Please restart Switchmatrix Program.";
                _modbusReadPending = false;
                _commEnabled = false;
                _failedConnection = true;
                _modbusCommPortOpen = true;
                return false;
            }
        }

        // ------------------------------------------------------------------------
        // Event for response data
        // ------------------------------------------------------------------------
        private void MBmaster_OnResponseData(ushort ID, byte function, byte[] values)
        {

            switch (ID)
            {
                default:
                // ignore Data from Write requests
                case C_WriteID:
                case 0xFF:
                {
                    _trackPlcTransmissions++;
                    return;
                }

                // Coil Read Request
                case C_ReadID:
                {
                    BitArray PlcRxData = new BitArray(values);
                    PlcRxData.CopyTo(BurkCommPlc_CoilsRead, 0);
                    _modbusReadPending = false;
                    ProcessCoils();
                    _trackPlcReceptions++;
                    _guiTextForeColor = Color.Black;
                    _guiText = "Burk Comm PLC Communication working normally";
                    break;
                }
                // Holding Register Read Request
                case H_ReadID:
                {
                    byte y = 0;
                    for (byte x = 0; x < (values.Length) / 2; x++)
                    {
                        Int16 tempVar = values[y++];
                        tempVar *= 256;
                        tempVar += values[y++];
                        BurkCommPlc_HoldingRegistersRead[x] = tempVar;
                    }
                    ProcessHoldingRegisters();
                    // The entire message has returned
                    _trackPlcReceptions += 10;
                    //_modbusReadPending = false;
                    break;
                }
            }
        }

        // ------------------------------------------------------------------------
        // Modbus TCP slave exception
        // ------------------------------------------------------------------------
        private void MBmaster_OnException(ushort id, byte function, byte exception)
        {
            string exc = "Burk PLC Error! Modbus: ";
            switch (exception)
            {
                case Master.excIllegalFunction: exc += "Illegal function!"; break;
                case Master.excIllegalDataAdr: exc += "Illegal data adress!"; break;
                case Master.excIllegalDataVal: exc += "Illegal data value!"; break;
                case Master.excSlaveDeviceFailure: exc += "Slave device failure!"; break;
                case Master.excAck: exc += "Acknowledge!"; break;
                case Master.excSlaveIsBusy: exc += "Slave is busy!"; break;
                case Master.excGatePathUnavailable: exc += "Gateway path unavailable!"; break;
                case Master.excExceptionTimeout: exc += "Slave timed out!"; break;
                case Master.excExceptionConnectionLost: exc += "Connection is lost!"; break;
                case Master.excExceptionNotConnected: exc += "Not connected!"; break;
            }
            log.Write(exc + "Modbus slave exception");
            _commEnabled = false;
            // Make sure that the systme forgets about the pending read
            _modbusReadPending = false;
            // Retry the connection
            _modbusCommPortOpen = false;
            OnPlcResponse(E_PlcEvent.Error2, _guiText, null);
        }

        private void ProcessCoils()
        {
            if (BurkCommPlc_CoilsRead[Convert.ToInt32(ReadCoilStatus.TransferRequested)])
            {
                for (byte x = 0; x < AppMain.SwMatrix.NumberOfTransmitters; x++)
                {
                    if (AppMain.SwMatrix._transmitterLocked[x + AppMain.SwMatrix.MaximumTransmitterNumber - AppMain.SwMatrix.NumberOfTransmitters + 1] != BurkCommPlc_CoilsRead[Convert.ToInt32(ReadCoilStatus.Lock_Block) + x])
                    {
                        _infoLog.Write(string.Format("Burk PLC: Transmitter {0} Lock. Was: {1}, Is: {2}", x + SwMatrix.MaximumTransmitterNumber - SwMatrix.NumberOfTransmitters + 1, AppMain.SwMatrix._transmitterLocked[x + AppMain.SwMatrix.MaximumTransmitterNumber - AppMain.SwMatrix.NumberOfTransmitters + 1], BurkCommPlc_CoilsRead[Convert.ToInt32(ReadCoilStatus.Lock_Block) + x]));
                    }
                    AppMain.SwMatrix._transmitterLocked[x + AppMain.SwMatrix.MaximumTransmitterNumber - AppMain.SwMatrix.NumberOfTransmitters + 1] = BurkCommPlc_CoilsRead[Convert.ToInt32(ReadCoilStatus.Lock_Block) + x];
                }
                // Initiate a read of holding registers if this is the first sample after the input went high
                if (!_transferRequestReceived)
                {
                    _dataVerified = false;
                }
                _transferRequestReceived = true;
            }
            else
            {
                _transferRequestReceived = false;
            }
            if (BurkCommPlc_CoilsRead[Convert.ToInt32(ReadCoilStatus.Reset)])
            {
                _infoLog.Write("Burk PLC: Reset Received from Burk");
                AppMain.SwMatrix._resetPressed = true;
            }
            // Check Antenna Ready Status.
            // This should be an atomic operation and the data is only read and written in one place,
            // so no mutex is needed.
            for( byte x = 0; x < AppMain.SwMatrix.NumberOfAntennas; x++)
            {
                if (AppMain.SwMatrix._unreadyAntennas[x] != BurkCommPlc_CoilsRead[Convert.ToInt32(ReadCoilStatus.AntennaNotReady) + x])
                {
                    _infoLog.Write(string.Format("Burk PLC: Antenna {0} Not Ready. Was: {1}, Is: {2}", x + 1, AppMain.SwMatrix._unreadyAntennas[x], BurkCommPlc_CoilsRead[Convert.ToInt32(ReadCoilStatus.AntennaNotReady) + x]));
                }
                AppMain.SwMatrix._unreadyAntennas[x] = BurkCommPlc_CoilsRead[(int)ReadCoilStatus.AntennaNotReady + x];
            }
        }

        private void ProcessHoldingRegisters()
        {
            for (byte x = 0; x < AppMain.SwMatrix.NumberOfTransmitters; x++)
            {
                if (AppMain.SwMatrix._antennaSelected[x + AppMain.SwMatrix.MaximumTransmitterNumber - AppMain.SwMatrix.NumberOfTransmitters + 1] != (byte)(BurkCommPlc_HoldingRegistersRead[x] & 0xFF))
                {
                    _infoLog.Write(string.Format("Burk PLC: Antenna {0} Selected for Transmitter {1}", x + 1, x + AppMain.SwMatrix.MaximumTransmitterNumber - AppMain.SwMatrix.NumberOfTransmitters + 1));
                }
                AppMain.SwMatrix._antennaSelected[x + AppMain.SwMatrix.MaximumTransmitterNumber - AppMain.SwMatrix.NumberOfTransmitters + 1] = (byte)(BurkCommPlc_HoldingRegistersRead[x] & 0xFF);
            }
            // Data has been read in, it is Ok to initiate the transfer and to acknowledge receeipt
            // to the PLC
            _dataVerified = true;

            // Clear the PLC request to 'ANY' so that future writes don't require a
            // setup of every antenna.
            byte[] dataToWrite = new byte[8];
            dataToWrite[0] = 0;
            dataToWrite[1] = SwMatrix.NumberOfAntennas;
            dataToWrite[2] = 0;
            dataToWrite[3] = SwMatrix.NumberOfAntennas;
            dataToWrite[4] = 0;
            dataToWrite[5] = SwMatrix.NumberOfAntennas;
            dataToWrite[6] = 0;
            dataToWrite[7] = SwMatrix.NumberOfAntennas;
            _burkComm.WriteMultipleRegister(H_WriteID, BurkCommPlc_ReadHoldingRegisterAddress, dataToWrite);
            // The Transfer Request has been processed, so this flag is no longer necessary
            //_transferRequestReceived = false;
        }
    }
}
